//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.IO;
using System.IO.Packaging;
using System.Reflection;
using System.Text;
using System.Xml;
using OpenLS.Core.Serialization;

namespace OpenLS.Core
{
    ///<summary>
    /// Base class for all types of office document. It contains all the common functionality such as standard and core properties properties and serialization. Descendants include SpreadesheetDocument and WordDocument
    ///</summary>
    public abstract class OfficeDocument : INotifyPropertyChanged
    {
        private const string corePropertiesContentType = "application/vnd.openxmlformats-package.core-properties+xml";

        private const string corePropertiesNamespace =
            @"http://schemas.openxmlformats.org/package/2006/metadata/core-properties";

        private const string corePropertiesRelationship =
            @"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";

        private const string extendedPropertiesContentType =
            @"application/vnd.openxmlformats-officedocument.extended-properties+xml";

        private const string extendedPropertiesNamespace =
            @"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties";

        private const string extendedPropertiesRelationship =
            @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";


        private object _tag;
        /// <summary>
        /// Gets or sets an arbitrary object value that can be used to store custom information about this document.
        /// </summary>
        public object Tag
        {
            get { return _tag; }
            set { _tag = value; this.NotifyPropertyChanged("Tag"); }
        }

        private CoreProperties _coreProperties;
        private ExtendedProperties _extendedProperties;

        private string _fileName;

        public virtual string FileName
        {
            get { return _fileName; }
            set
            {
                _fileName = value;
                NotifyPropertyChanged("Name");
                NotifyPropertyChanged("FileName");
            }
        }

        public string Name
        {
            get
            {
                if (FileName == null)
                    return DefaultName;
                return Path.GetFileName(FileName);
            }
        }

        protected abstract string DefaultName { get; }

        private bool HasCoreProperties
        {
            get
            {
                if (_coreProperties == null)
                    return false;
                return _coreProperties.PropertyCount > 0;
            }
        }

        protected bool HasExtendedProperties
        {
            get { return ExtendedProperties != null; }
        }

        public CoreProperties CoreProperties
        {
            get
            {
                if (_coreProperties == null)
                    _coreProperties = new CoreProperties();
                return _coreProperties;
            }
            set { _coreProperties = value; }
        }

        public string Creator
        {
            get { return CoreProperties.Creator; }
            set { CoreProperties.Creator = value; }
        }

        public string Category
        {
            get { return CoreProperties.Category; }
            set { CoreProperties.Category = value; }
        }

        public string ContentStatus
        {
            get { return CoreProperties.ContentStatus; }
            set { CoreProperties.ContentStatus = value; }
        }

        public string ContentType
        {
            get { return CoreProperties.ContentType; }
            set { CoreProperties.ContentType = value; }
        }

        public DateTime? Created
        {
            get { return CoreProperties.Created; }
            set { CoreProperties.Created = value; }
        }

        public string Description
        {
            get { return CoreProperties.Description; }
            set { CoreProperties.Description = value; }
        }

        public string Identifier
        {
            get { return CoreProperties.Identifier; }
            set { CoreProperties.Identifier = value; }
        }

        public string Keywords
        {
            get { return CoreProperties.Keywords; }
            set { CoreProperties.Keywords = value; }
        }

        public string Language
        {
            get { return CoreProperties.Language; }
            set { CoreProperties.Language = value; }
        }

        public string LastModifiedBy
        {
            get { return CoreProperties.LastModifiedBy; }
            set { CoreProperties.LastModifiedBy = value; }
        }

        public DateTime? LastPrinted
        {
            get { return CoreProperties.LastPrinted; }
            set { CoreProperties.LastPrinted = value; }
        }

        public string Modified
        {
            get { return CoreProperties.Modified; }
            set { CoreProperties.Modified = value; }
        }

        public string Revision
        {
            get { return CoreProperties.Revision; }
            set { CoreProperties.Revision = value; }
        }

        public string Subject
        {
            get { return CoreProperties.Subject; }
            set { CoreProperties.Subject = value; }
        }

        public string Title
        {
            get { return CoreProperties.Title; }
            set { CoreProperties.Title = value; }
        }

        public string Version
        {
            get { return CoreProperties.Version; }
            set { CoreProperties.Version = value; }
        }

        protected ExtendedProperties ExtendedProperties
        {
            get { return _extendedProperties; }
            set
            {
                _extendedProperties = value;
                NotifyPropertyChanged("ExtendedProperties");
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        public void SaveAs(string path)
        {
            byte[] backup = null;
            if (File.Exists(path))
            {
                using (Stream reader = File.Open(path, FileMode.Open))
                {

                    backup = new byte[reader.Length];
                    reader.Read(backup, 0, backup.Length);
                }
                File.Delete(path);
            }
            try
            {
                using (Package package = Package.Open(path))
                {
                    WritePackage(package);
                    SetNotDirty();
                    FileName = path;
                }
            }
            catch
            {
                if (backup != null)
                {
                    using (Stream reader = File.Open(path, FileMode.Open, FileAccess.Write))
                    {
                        reader.Write(backup, 0, backup.Length);
                    }
                }
                throw;
            }
        }

        protected abstract void SetNotDirty();

        ///<summary>
        /// Save the office document to disk. If the FileName property has not been set, call SaveAs instead.
        ///</summary>
        ///<exception cref="InvalidOperationException"></exception>
        public virtual void Save()
        {
            if (FileName == null)
                throw new InvalidOperationException();
            SaveAs(FileName);
        }

        protected void ReadCoreProperties(Package package)
        {
            PackageRelationship rel = null;
            foreach (PackageRelationship relationship in package.GetRelationships())
            {
                if (relationship.RelationshipType == corePropertiesRelationship)
                    rel = relationship;
            }
            if (rel != null)
            {
                PackagePart corePropertiesPart =
                    package.GetPart(PackUriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri));
                using (
                    ReadContext corePropertiesReadContext = ReadContext.Open(corePropertiesPart, rel,
                                                                             corePropertiesNamespace))
                {
                    CoreProperties = corePropertiesReadContext.ReadElement<CoreProperties>();
                }
            }
        }

        protected void WriteCoreProperties(Package package)
        {
            if (HasCoreProperties)
            {
                var corepropertiesUri = new Uri("/docProps/core.xml", UriKind.Relative);
                PackageRelationship rcp = package.CreateRelationship(corepropertiesUri, TargetMode.Internal,
                                                                     corePropertiesRelationship);
                Uri ucp = PackUriHelper.ResolvePartUri(rcp.SourceUri, rcp.TargetUri);
                PackagePart propertiesPart = package.CreatePart(ucp, corePropertiesContentType,
                                                                CompressionOption.Maximum);
#if !SILVERLIGHT
                using (Stream stream = propertiesPart.GetStream())
                using (XmlWriter xmlWriter = new XmlTextWriter(stream, Encoding.UTF8))
                {
                    var ctx = new WriteContext(null, corePropertiesNamespace, xmlWriter, propertiesPart);
                    ctx.WriteElement(CoreProperties);
                }
#endif
            }
        }

        protected void WriteExtendedProperties(Package package)
        {
            if (HasExtendedProperties)
            {
                var extendedPropertiesUri = new Uri("/docProps/app.xml", UriKind.Relative);
                PackageRelationship rcp = package.CreateRelationship(extendedPropertiesUri, TargetMode.Internal,
                                                                     extendedPropertiesRelationship);
                Uri ucp = PackUriHelper.ResolvePartUri(rcp.SourceUri, rcp.TargetUri);
                PackagePart propertiesPart = package.CreatePart(ucp, extendedPropertiesContentType,
                                                                CompressionOption.Maximum);
#if !SILVERLIGHT
                using (Stream stream = propertiesPart.GetStream())
                using (XmlWriter xmlWriter = new XmlTextWriter(stream, Encoding.UTF8))
                {
                    var ctx = new WriteContext(null, extendedPropertiesNamespace, xmlWriter, propertiesPart);
                    ctx.WriteElement(ExtendedProperties);
                }
#endif
            }
        }

        public abstract void WritePackage(Package package);

        public virtual void ReadPackage(Package package)
        {
            ReadPackage(package, null);
        }

        public abstract void ReadPackage(Package package, BackgroundWorker opContext);

        protected void NotifyPropertyChanged(string propertyName)
        {
            Contract.Requires(
                GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) !=
                null);
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        protected void ReadExtendedProperties(Package package)
        {
            PackageRelationship rel = null;
            foreach (PackageRelationship relationship in package.GetRelationships())
            {
                if (relationship.RelationshipType == extendedPropertiesRelationship)
                    rel = relationship;
            }
            if (rel != null)
            {
                PackagePart corePropertiesPart =
                    package.GetPart(PackUriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri));
                using (
                    ReadContext extendedPropertiesReadContext = ReadContext.Open(corePropertiesPart, rel,
                                                                                 extendedPropertiesNamespace))
                {
                    ExtendedProperties = extendedPropertiesReadContext.ReadElement<ExtendedProperties>();
                }
            }
        }
    }
}